1 What is bsmdoc?
bsmdoc is a tool to generate technical static html docs:
- Light-weighted;
- Highly extendable;
- Single file doc: generate everything in a single file.
bsmdoc splits the whole doc into blocks (e.g., equation block, paragraph block, heading block, ...). In the following sections, we will show each block in detail, as well as the way to extend the existing blocks.
1.1 Installation
bsmdoc can be installed with pip
$ pip install bsmdoc
You can also clone the repository and run setup.py
$ pip install -e .
1.2 Get Started
Create a file (e.g., helloworld.bsmdoc) with content
= hello world
Compile the file
$ bsmdoc helloworld.bsmdoc
It will generate helloworld.html in the same folder, which may look like
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="generator" content="bsmdoc 0.0.9"> <link rel="stylesheet" href="css/bsmdoc.css" type="text/css"> </head> <body class="nomathjax"> <div class="layout"> <div class="main"> <div class="content"> <h1>hello world</h1> </div> </div> <div class="footer"> <div class="footer-text"> Last updated 2023-12-27 22:06:03 UTC by <a href="http://bsmdoc.feiyilin.com/">bsmdoc</a>.</div> </div> </div> </body> </html>
1.3 1-minute Tutorial
bsmdoc also provides some commands to create project and .bsmdoc.
First, create a project
$ mkdir myprj $ cd myprj $ bsmdoc init
The last command will
- copy the default css and javascript files to the current folder;
- create index.bsmdoc from template;
- generate index.html from index.bsmdoc.
Now, you can update the index.bsmdoc and then regenerate the html
$ bsmdoc index.bsmdoc
Add a new doc from template to current folder
$ bsmdoc new page
It will create the page.bsmdoc from template.
2 Heading
heading block is defined by a line starting with =
level 1
level 2
level 3
level 4
level 5
level 6
= level 1 == level 2 === level 3 ==== level 4 ===== level 5 ====== level 6
As you have seen, heading block must start at a new line. Otherwise, =
(e.g., in a paragraph) is just normal equal sign.
bsmdoc supports all 6 heading levels as shown above. However, we don't think anyone needs all these 6 levels (remember, "The Feynman Lectures on Physics" only uses 2 levels).
heading text can spread over multiple lines as long as they are enclosed by "{}
", as shown in the following example. Actually, "{}
" also define a basic block in bsmdoc. It is quite flexible to define the heading text; generally, you are allowed to put everything in it.
multiple
line
heading
multiple line heading
= {multiple\n line\n heading} = {multiple line heading}
To add reference to the heading block, first you need to add a label with command "\label
". Then, it can be referenced as a normal in-page link.
section with label
This section ...
= {section with label \label{sec-label}} This [#sec-label|section] ...
By default, the heading block will not be automatically numbered. The following configuration is used to turn on the automatic numbering
\config{heading_numbering|True}
bsmdoc will not start the automatic heading numbering until you set the above flag. Thus if you want bsmdoc to add numbering to the whole doc, simply put the above configuration line at the beginning of the doc. You also can set the start heading level for automatic numbering. For example, the following line will tell bsmdoc to start heading numbering from level 2 (i.e., H2
)
\config{heading_numbering_start|2}
When referencing to a heading block with automatic heading numbering on, the link text will be automatically filled with its index if it is empty,
Sec. [#sec-heading] ...
It can also be achieved by \ref
command
Sec. \ref{sec-heading}...
bsmdoc will generate the table of contents from the headings, if the following configuration is enabled
\config{show_table_of_contents|True}
Only the headings with automatic numbering will be included in the content list. The table of contents is generated by a function block makecontent. Its input is a list that contains all the headings. The default implementation generates an un-ordered list
@BFunction('makecontent') def bsmdoc_makecontent(contents, **kwargs): """ table of contents is a list, each item [level, text, label] level: 1~6 text: the caption text label: the anchor destination """ if not contents: return "" first_level = min([c[0] for c in contents]) call = [] for c in contents: # the text has been parsed, so ignore the parsing here txt = BFunction().tag(c[1], 'a', 'href="#%s"' % c[2]) call.append(['-' * (c[0] - first_level + 1), txt]) return BFunction().listbullet(call)
3 List
bsmdoc defines two kinds of list blocks: unordered list and ordered list.
A line leading with "-
" will be rendered as unordered list. "-
" will be ignored if it is not at the start of a line. Such rule holds for all heading, unordered list, and ordered list blocks.
- start each line
- with an hyphen -.
- more asterisks gives deeper
- and deeper levels.
- more asterisks gives deeper
- line breaks
don't break levels.- but jumping levels creates empty space.
- start each line - with an hyphen -. -- more asterisks gives deeper --- and deeper levels. - line breaks\n don't break levels. --- but jumping levels creates empty space. any other start ends the list.
Ordered list starts with "*
"
- start each line
- with a start '*'
- more asterisks gives deeper
- and deeper levels.
- more asterisks gives deeper
- line breaks
don't break levels.- but jumping levels creates empty space.
* start each line * with a start '*' ** more asterisks gives deeper *** and deeper levels. * line breaks\n don't break levels. *** but jumping levels creates empty space. any other start ends the list.
unordered and ordered lists can also be arbitrarily combined
- unordered level 1
- ordered item 1
- ordered item 2
- unordered item 3
- unordered item 4
- unordered level 1 -* ordered item 1 -* ordered item 2 -- unordered item 3 -- unordered item 4
Each item of a list can spread over multiple lines as long as they are enclosed by "{}
":
- unordered level 1
more text here bsmdoc- ordered item2
- {unordered level 1\n more text here [bsmdoc.feiyilin.com | bsmdoc] } -* ordered item2
4 Link
Text between [
and ]
will be rendered as link block
[http://bsmedit.feiyilin.com]
Link text can be customized with "|
"
[http://bsmdoc.feiyilin.com | bsmdoc]
Or email
[mailto:tq@feiyilin.com | Email]
You can also easily define in-page links with \anchor
command
In page link \anchor{myanchor}
Then, you can link to the in-page link by
My in-page [#myanchor|link]
4.1 Footnote
One special in-page link is footnote
⚓. It will automatically add the link to the position it gets defined, and add the footnote content at the end of the page. bsmdoc also adds a shortcut at the end of the footnote content, so you can return to the footnote definition position easily.
footnote example\footnote{This is a footnote}.
bsmdoc will automatically add indexing to each footnote. When you move the cursor to the footnote link, the footnote content will be shown in a popup window. So you may not need to go to the end of the page to see the footnote content. The image, equation and table blocks support similar feature.
4.2 Reference & Citation
bsmdoc has an easy way to add references and citations. For example, the following command will add a book reference
\reference{Simon|Simon Haykin, "Adaptive Filter Theory," Prentice Hall, 4th edition, Sep. 2001}
Simon before "|
" is the alias, which can be used to cite it, e.g.,
The LMS algorithm in \cite{Simon}...
And the reference is not required to be defined before citation. If bsmdoc can not find the reference when it sees \cite
, it will trigger the second scan, in case the reference is defined after the citation. If the reference is still missing at the second scan, bsmdoc will show a warning.
All cited references will be appended to the end of the document in the citation order. If you want to add the reference without citation, you can use the hide
argument of the cite
command
\cite{hide|Simon}...
In this case, it will not generate the link to the reference, but the reference will still be added to the reference list even if there is no explicit citation in the document.
5 Image & Video
5.1 Image
The syntax of the image block is
{!image|| image_file_path !}
So, to include an image
{!image|| ./image/scatter.svg !}
You can add optional caption to an image block
{!image|| \caption{Example scatter image} ./image/scatter.svg !}
To add reference to an image, you need to
add a label to an image block with
\label
command{!image|| \label{img-scatter} \caption{Example scatter image} ./image/scatter.svg !}
add reference to the image with
\ref
commandFig. 3 shows a scatter diagramFig. \ref{img-scatter} shows a scatter diagram
Here the reference link \ref{img-scatter}
) is defined after the definition of the image block. The reference link text is automatically replaced with the image index. In some case, if the reference link is created before the image block is defined, bsmdoc will not know the destination when it sees the reference link. In this case, the second scan will automatically be triggered to solve the reference link.
When the cursor is moved to the reference link, the referenced image will be highlighted if it is visible; otherwise, the image will be shown in a popup window. Such feature is inspired by "The Feynman Lectures on Physics" website. It allows you to view the images at the current reference position. Otherwise, you would have to follow the link to the original place where the image is first included. To use such feature, the image label should start with "img-
", which is hard-coded in the Javascript. The references to equation, table, and footnote behave similarly.
As you have seen, the image block can automatically add the numbering to the image with label. The default automatic indexing format is: "Fig. I.
", where "I
" is the current index. You can configure the automatic prefix text. For example, to change it to "Image
", the following line can be inserted before a image block definition (usually at the beginning of the doc, so that it affects all the image blocks)
\config{image_numbering_prefix|Image }
For example
{!image|| \config{image_numbering_prefix|Image } \label{img-scatter2} \caption{Example scatter image} ./image/scatter.svg !}
The options to configure the image numbering
Option | Description |
---|---|
image_numbering | True or False (default). Turn on/off numbering, e.g., \config{image_numbering|True} |
image_numbering_prefix | The numbering prefix (default "Fig."). To change the prefix, e.g., \config{image_numbering_prefix|Image.} |
image_numbering_num_prefix | The numbering prefix (default ""). To change the prefix, e.g., \config{image_numbering_num_prefix|3.} |
5.2 Video
Video block is almost same as the image block. Its syntax is
{!video|| video_file_path !}
For example, to add reference to an image, you need to
add a label to an image block with
\label
command{!video|| \label{video-waves} \caption{Example video} ./image/waves.mp4 !}
add reference to the image with
\ref
commandVid. 5 shows a scatter diagramVid. \ref{video-waves} shows a scatter diagram
The options to configure the video numbering
Option | Description |
---|---|
video_numbering | image (default) or True or False. Turn on/off numbering. If it is set to image, then it will share numbering with image block. |
video_numbering_prefix | The numbering prefix (default "Video."). Only valid if video_numbering is not image. |
video_numbering_num_prefix | The numbering prefix (default ""). Only valid if video_numbering is not image. |
6 Equation
Equation is rendered with mathjax: both inline and normal equation blocks. The configuration may look like:
<script> MathJax = { tex: { inlineMath: [['\\(', '\\)']], tags: "all" } }; </script> <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3.0.0/es5/tex-mml-chtml.js"> </script>
Such configuration can be included in a configuration file, which will be shown in detail in Sec. 12.
bsmdoc looks for "\(...\)
" (or "$...$
") as delimiters for inline equation block
Newton's second law is often stated as \(F=ma\), which means the force (\(F\)) acting on an object is equal to the mass ($m$) of an object times its acceleration ($a$).
The normal equation block looks like
{!math||{% Latex Equation %}!}
or
$$ Latex Equation $$
For example
{!math||{% \begin{align} \begin{bmatrix} 0 & \cdots & 0 \\ \vdots & \ddots & \vdots \\ 0 & \cdots & 0 \label{eqn:matrix} \end{bmatrix} \end{align} %}!}
bsmdoc generally keeps the equation content (i.e., the content between {%...%}
) untouched, except <
and >
, which will be replaced with <
and >
respectively, to avoid conflicts with html tags. For example,
$a > 5$
$a>5$
Unlike the image block mentioned above, the reference to equation is also handled by mathjax. The syntax is same as \(Latex\)
Eq. (\ref{eqn:matrix}) or Eq. (\eqref{eqn:matrix})
As mentioned above, same as the image block, when move the cursor to the equation reference link, bsmdoc will highlight the referenced equation if it is visible. Otherwise, a popup window will be displayed to show the equation.
7 Table
The table block is defined with {{...}}
, where
- each row is ended by "
|-
"; - each column in a row is terminated by "
|
".
The content in each column of each row can be anything defined above, for example, Link, Image, Equation,...
item1 | \(E=MC^2\) |
bsmdoc |
{{ item1 | $E=MC^2$ ||- [http://bsmdoc.feiyilin.com|bsmdoc] | {!div|figure|image-left|image-thumbnail||\image{image/scatter.svg}!} ||- }}
Optionally, a heading line can be added in the front of the table block
the heading line is delimited by "
|+
".Heading1 Heading2 item1 item2 item3 item4 {{ Heading1 | Heading2||+ item1 | item2 ||- item3 | item4 ||- }}
Furthermore, a caption can be added to the table block too
Heading1 | Heading2 |
---|---|
item1 | item2 |
item3 | item4 |
{{ \caption{Example table title} Heading1 | Heading2||+ item1 | item2 ||- item3 | item4 ||- }}
bsmdoc can also add the automatic indexing to the table caption. To do that, you need to
turn on the option
\config{table_numbering|True}
bsmdoc will add the automatic indexing to all the tables defined after the above line, until you explicitly turn off the configuration.
add the label to each table if you want to reference it
\label{table_label}
For example
Heading1 | Heading2 |
---|---|
item1 | item2 |
item3 | item4 |
{{ \config{table_numbering|True} \label{tbl-example} \caption{Example table title} Heading1 | Heading2||+ item1 | item2 ||- item3 | item4 ||- }}
The default automatic indexing format is: "Table. I.
", where "I
" is the current index. It can also be customized. For example, to change it to "TABLE
", the following line can be inserted before the table block definition
\config{table_numbering_prefix|TABLE }
Heading1 | Heading2 |
---|---|
item1 | item2 |
item3 | item4 |
{{ \config{table_numbering_prefix|TABLE } \label{tbl-example2} \caption{Example table title} Heading1 | Heading2||+ item1 | item2 ||- item3 | item4 ||- }}
The label enables not only the automatic indexing, but also the cross-reference. Reference to a table is as easy as image and equation blocks
Table \ref{tbl-example} shows how to make a table in bsmdoc.
If the table label text starts with "tbl-
", when the cursor is moved to the reference index, the table will be highlighted if it is visible; otherwise, a popup window will be displayed to show the table content.
The options to configure the table numbering
Option | Description |
---|---|
table_numbering | True or False (default). Turn on/off numbering. |
table_numbering_prefix | The numbering prefix (default "Table."). |
table_numbering_num_prefix | The numbering prefix (default ""). |
8 Syntax Highlighting
bsmdoc uses Pygments for syntax highlighting. The syntax is
{!highlight|language||{% code %}!}
where language
can be any language supported by Pygments. For example,
Python
print("Hello World")
{!highlight|python||{% print("Hello World") %}!}
Matlab
fprintf(1, 'Hello, world!\n');
{!highlight|matlab||{% fprintf(1, 'Hello, world!\n'); %}!}
C++
#include <iostream> using namespace std; int main () { cout << "Hello World!"; return 0; }
{!highlight|C++||{% #include <iostream> using namespace std; int main () { cout << "Hello World!"; return 0; } %}!}
The highlight
block supports a couple of options to format the code:
- obeytabs: to replace tab with 4 space
gobble=N: remove the first N characters from each line.
print("Hello World")
{!highlight|python|gobble=4||{% print("Hello World") %}!}
- autogobble: remove leading common white space from each line.
All other keyword arguments will be forwarded to HtmlFormatter. The following example turns on the line number, and highlights line 6.
1 2 3 4 5 6 7 8
#include <iostream> using namespace std; int main () { cout << "Hello World!"; return 0; }
{!highlight|C++|linenos=table|hl_lines=(6,)||{% #include <iostream> using namespace std; int main () { cout << "Hello World!"; return 0; } %}!}
It is also possible to use other packages for syntax highlighting, which will be discussed in detail in Sec. 10.
9 Raw Text Block
Sometimes you may want to skip the parsing from bsmdoc. For example, to highlight the code, it is basically not a good idea to let bsmdoc parse the code. Instead, the raw data should be sent to the code highlighting block. In another example, you may have seen similar syntax between the latex macro and the bsmdoc commands (e.g., both are preceded by "\
"). If bsmdoc tries to parse these latex macros, the result is obviously not what you want. Thus, bsmdoc defines the raw text block. It basically tells bsmdoc to ignore all the rules on its content.
The syntax of the raw text block is
{% Content %}
In this case, bsmdoc will stop parsing the Content
between {%...%}
, and the raw text will be either sent to the final html file or send to the other function blocks to process.
However, sometimes you may still want to slightly process the Content
before sending to the html file. For example, the Content
may contain symbols (e.g., < >
), which may cause conflict to the html tags. You can use the escape function block to process the data
{!escape||{% Content %}!}
In this case, <
and >
will be replaced with <
and >
, respectively.
<
(>
) with <
(>
). Thus, there is no need to explicitly call the escape function block.
By symmetry, bsmdoc also defines the unescape function block, which will replace <
(>
) with <
(>
).
10 Function Block
Function block makes things really interesting. Without function block, bsmdoc is almost useless. Function block provides a easy way to allow the content to be processed before sent to the final html file. As you have seen above, the syntax of a function block looks like
{!command content !}
where command
is optional and may contain the name and arguments of the corresponding function block.
As described above, many features mentioned above are implemented with function blocks. For example,
image block:
{!image|| image-path !}
equation block:
{!math||{% latex-equation %}!}
syntax highlight block:
{!highlight|language||{% code %}!}
div tag
<div class="div-class"> content </div>
{!div|div-class|| content !}
pre tag
<pre> content </pre>
{!pre||{% content %}!}
general tag
<key class="tag-class"> content </key>
{!tag|key|tag-class|| content !}
The command
section is optional. When the command
section is not defined, the function block behaves exactly same as the simple block ({}
). Otherwise, the corresponding function will be called to process the content.
As you may have already figured out, the command
section is defined as
command|arg0|arg1|arg2|...|argN||
The command can have arbitrary number of arguments, which are separated by "|
". And the command is always terminated by "||
". It is also possible to combine multiple commands, e.g.,
command0|arg0|arg1|arg2||command1|arg0|arg1|arg2||
In this case, the commands are called from right to left. Thus in the above example, command1
will be called first, and its result will be sent to command0
. The result from command0
will be sent to the html file. It is equivalently to the following nested blocks
{!command0|arg0|arg1|arg2|| {!command1|arg0|arg1|arg2|| content !}!}
The function block makes it easy to extend bsmdoc. We could have defined all kinds of function blocks to generate all html tags, and write a comprehensive doc so we can look up later. We don't. Otherwise, that will not be significantly different from remembering all html syntax. Instead, we make it easy to add new functions.
When bsmdoc sees the following function block
{!command|arg0|arg1|| content !}
it will search for the function decorated with "@BFunction('command')
". If found, it will execute the command with list "[arg0, arg1]
" as argument. For example, if you want to define a function block to achieve the "delete" effect,
first you need to define the function (e.g., "
bsmdoc_del
") with decorator "@BFunction('del')
"; the function name (bsmdoc_del
) is not important.@BFunction('del') def bsmdoc_del(data, *args, **kwargs): return "<del>%s</del>"%data
*args
will have all the arguments from the function block. For example, ifdel
is called as{!del|arg0|arg1|| content !}
then bsmdoc will call
bsmdoc_del
bybsmdoc_del("content", "arg0", "arg1", **kwargs)
Here
**kwargs
are additional arguments from bsmdoc, for exampleargument description inline True
if it is a inline block (we will discuss the inline block shortly).filename the current filename lineno the line number of the start of the function block fun_args the list string arguments from function block fun_kwargs the dict of keyword arguments from function block Such info may be helpful to show debugging tips once some error happens.
bsmdoc will also parse the arguments from the function. All string arguments will be in list fun_args (e.g., 'args0') and keyword arguments will be in dict fun_kwargs (e.g., argument key=value will be convert to fun_kwargs[key]=value). In this case, if value is a number, it will be convert to a number, e.g., "answer=42" will be fun_kwargs['answer']=42; otherwise, value will be a string, e.g., "today=sunday" will be fun_kwargs['today']='sunday'.
For a argument to be recognized as keyword argument, first, it has to have an "
=
" in it; second, the string to the left of the first "=
" shall be a valid python identifier.Now
del
(the name from@BFunction
decorator) can be used as all the other blocks,<del>delete</del>
{!del|| delete !}
All command
functions shall return a string, which will be sent to the next block or the html file. If the function does not need to change the html file, it shall return the empty string.
The next question is where to put the function definition? One straightforward solution is to define the above bsmdoc_del
function in bsmdoc.py and then all the documents can use the del
function block. However, that also means once you need some missing features, you need to go back to the bsmdoc.py and define the functions there. It may cause two potential issues:
- It needs to edit additional file bsmdoc.py besides the docs you are working on. Thus the change is not local to the doc itself. If there is some mistake in the code, suddenly no doc can be compiled;
- How about two docs need slightly different implementation of some function? You may end up either using some slightly different (but not straightforward) function names, or adding additional arguments to the function blocks.
Neither way is scalable. bsmdoc provides the third choice: the function block can be defined in the same doc where it is called. To achieve that, bsmdoc define a special exec
block
{!exec||{% code %}!}
which will execute the code
automatically.
Thus, including the following code in your doc will automatically add the bsmdoc_del
function just as it is defined in bsmdoc.py (but be careful, since bsmdoc will call python function "exec()
" for this feature, it may cause some security concerns).
{!exec||{% @BFunction('del') def bsmdoc_del(data, *args, **kwargs): return "<del>%s</del>"%content %}!}
With the exec
block, you can replace all the predefined blocks with this method. For example, you can define your code highlight function block so that you can highlight your own language or with other library
{!exec||{% @BFunction('highlight') def bsmdoc_highlight(data, *args, **kwargs): ... %}!}
Or in some case, you just want to use your own function for some arguments. For example, you want to call your own function to highlight the code for a specific language, and use the default function for all the others. In this case, you may try
{!exec||{% bsmdoc_highlight_raw = BFunction().highlight # get the current highlight function @BFunction('highlight') def bsmdoc_highlight(data, *args, **kwargs): if args[0] != 'mylang': return bsmdoc_highlight_raw(data, *args, **kwargs) # highlight 'mylang' ... %}!}
It may work fine to compile most of your docs, until one day it fails and python complains indefinite loop. The problem here is that the above definition assumes such code will only be called once. But it is not always true for bsmdoc. As mentioned before, bsmdoc may scan the doc multiple times to solve the late-defined references. Thus, when bsmdoc executes the above code second time, bsmdoc_highlight_raw
will not refer to the original bsmdoc_highlight
, instead, it will also point to the local defined copy. The definition will be equivalent to
{!exec||{% bsmdoc_highlight_raw = BFunction().highlight @BFunction('highlight') def bsmdoc_highlight(data, *args, **kwargs): if args[0] != 'mylang': return bsmdoc_highlight(data, *args, **kwargs) # highlight 'mylang' ... %}!}
It is apparently an indefinite loop.
Several techniques can be used to solve this problem. For example, you can always define the whole function by yourself, instead of calling the default function in some branches. Or you may check whether the local function has been defined or not
{!exec||{% try: bsmdoc_highlight_raw except NameError: bsmdoc_highlight_raw = BFunction().highlight @BFunction('highlight') def bsmdoc_highlight(data, *args, **kwargs): if args[0] != 'mylang': return bsmdoc_highlight(data, *args, **kwargs) # highlight 'mylang' ... %}!}
Or you can tell bsmdoc to execute the code for the first scan only
{!exec|firstRunOnly||{% bsmdoc_highlight_raw = BFunction().highlight @BFunction('highlight') def bsmdoc_highlight(data, *args, **kwargs): if lang != 'mylang': return bsmdoc_highlight(data, *args, **kwargs) # highlight 'mylang' ... %}!}
10.1 Generate Images
Besides defining the function block, the exec
block can also be used to execute arbitrary python code (be careful!). One application is to embed the python code to generate the figure with matplotlib package,
{!exec||{% import os.path if not os.path.isfile("pie.svg"): import matplotlib.pyplot as plt import numpy as np plt.clf() plt.figure(figsize=(4,4)) # Compute pie slices N = 20 theta = np.linspace(0.0, 2 * np.pi, N, endpoint=False) radii = 10 * np.random.rand(N) width = np.pi / 4 * np.random.rand(N) ax = plt.subplot(111, projection='polar') bars = ax.bar(theta, radii, width=width, bottom=0.0) # Use custom colors and opacity for r, bar in zip(radii, bars): bar.set_facecolor(plt.cm.viridis(r / 10.)) bar.set_alpha(0.5) plt.savefig("image/pie.svg") %}!}
Then you can include the pie.svg in your html file
{!image|| ./image/pie.svg !}
Thus, there may be no need to use other software to generate figures.
10.2 Include Source Code
With function block, it is easy to include source code in html doc. For example, to import the python source code in your doc, you can define the following function block
{!exec|firstRunOnly||{% import inspect import six @BFunction('codesnippet') def bsmdoc_codesnippet(data, *args, **kwargs): d = eval(data) if isinstance(d, six.string_types): return d else: return inspect.getsource(d) %}!}
Then you can include the source code (e.g., matplotlib.pyplot.plot) by
@_copy_docstring_and_deprecators(Axes.plot) def plot( *args: float | ArrayLike | str, scalex: bool = True, scaley: bool = True, data=None, **kwargs, ) -> list[Line2D]: return gca().plot( *args, scalex=scalex, scaley=scaley, **({"data": data} if data is not None else {}), **kwargs, )
{!exec|firstRunOnly||{% import matplotlib.pyplot as plt %}!} {!highlight|python||codesnippet|| plt.plot !}
It can be easily extended to include arbitrary code. For example, the following block returns the content of a source file. Of course, you can write a function block to just return certain sections (e.g., a function), instead of the whole file.
{!exec|firstRunOnly||{% @BFunction('ccodesnippet') def bsmdoc_ccodesnippet(data, *args, **kwargs): with open(data.strip(), 'r') as f: return ''.join(f.readlines()) return data %}!}
Then you can include the source code by
$(window).on("load", function() { // Cache selectors var lastId, topMenu = $(".menu"), topTitleHeight = $(".toptitle").outerHeight()+15, topUL = topMenu.find('ul'), // All list items menuItems = topMenu.find("a"), // Anchors corresponding to menu items scrollItems = menuItems.map(function(){ var item = $($(this).attr("href")); if (item.length) { return item; } }); var auto_hide_child_menu = menuItems.length > 15; // Bind click handler to menu items so we can get a fancy scroll animation if (!auto_hide_child_menu) { menuItems.click(function(e){ var href = $(this).attr("href"), offsetTop = href === "#" ? 0 : $(href).offset().top-15+1; $('html, body').stop().animate({ scrollTop: offsetTop }, 300); e.preventDefault(); }); } function update_menu() { // Get container scroll position var fromTop = $(this).scrollTop() + 15; // Get id of current scroll item var cur = scrollItems.map(function(){ if ($(this).offset().top < fromTop) return this; }); // Get the id of the current element cur = cur[cur.length-1]; var id = cur && cur.length ? cur[0].id : ""; if (lastId !== id) { lastId = id; // Set/remove active class menuItems.removeClass("active"); menuItems.filter("[href='#"+id+"']").addClass("active"); if (auto_hide_child_menu) { topUL.find('ul').each(function(index){ $(this).css('display', 'none'); }); } menu = menuItems.filter("[href='#"+id+"']"); menuul = menu.closest('ul').css('display', 'inline-block'); menu.closest('li').children('ul').css('display', 'inline-block'); } } // Bind to scroll $(window).scroll(function(){ update_menu(); }); update_menu(); });
{!highlight|javascript||ccodesnippet|| js/menu.js !}
10.3 Inline Function Block
The difference between function block and inline function block is similar to the equation block and inline equation block. In other words, the output of the inline block can be embedded in paragraphs. The syntax is similar to the \(\LaTeX\) macro:
\command{arg0|arg1|...|argN|content}
In general, the blocks defined in the above section can also be used as inline block. For example
<code>content</code> <pre>content</pre>
\tag{code|content} \pre{content}
bsmdoc defines a special inline function block for configuration (\config{}
). It adds a configuration item to bsmdoc's global configuration table, and output empty string to the html file. Thus, it will not directly update the html docs, although the other blocks may rely on such configurations to control their behaviors. The syntax is
\config{option|value}}
We have seen heading, image, and table blocks use such feature to set the label and caption. We will see more configurations in Sec. 12.
10.4 Express Function Block
Besides the way mentioned above, there is a shortcut to define a function block, if it returns a simple string. The syntax to define a function is
\newfun{name|content}
For example
\newfun{bsmdoc|\tag{strong|bsmdoc}}
Once it is defined, it can be used as
\bsmdoc
where \bsmdoc
will be replaced with the result of \tag{strong|bsmdoc}
, i.e.,
<strong>bsmdoc</strong>
11 Input Redirection
When the doc becomes large, you can split your doc into several docs and includes them in the top doc with the "#include
" directive. For example, suppose you are writing a book, and have created chappter1.bsmdoc, chapter2.bsmdoc, .... Then, you can include all the chapters in your book.bsmdoc
#include chapter1.bsmdoc #include chapter2.bsmdoc
bsmdoc will replace the line #include chapter1.bsmdoc with the content of the file chappter1.bsmdoc.
The #include
is implemented with function bsmdoc_include
@BFunction('include') def bsmdoc_include(data, **kwargs): filename = data.strip() if os.path.isfile(filename): return _bsmdoc_readfile(filename, **kwargs) else: _bsmdoc_error("can't not find %s" % filename, **kwargs) return ""
Here data
will be anything following #include
. In the previous example, it will be "chapter1.bsmdoc" or "chapter2.bsmdoc". If the file is opened successfully, by default bsmdoc_include will return the filename and its content.
It is easy to extend the bsmdoc_include function for more advanced applications, for example
{!exec|firstRunOnly||{% bsmdoc_include_raw = BFunction().include @BFunction('include') def bsmdoc_include(data): # assume 'data' has multiple sections separated by '|', in the format of # PATTERN | MAX LINE | FILENAME d = data.strip().split('|') if len(d) == 1: # one section, return the default return bsmdoc_include_raw(data) elif len(d) == 3: import re # assume the last parameter is the filename c = bsmdoc_include_raw(d[-1]) if not c: # invalid filename return c lines = c[1].split('\n') pattern = [] for i, l in enumerate(lines): # search the PATTERN if re.match(d[0], l): pattern.append(i) if len(pattern) == 2: # return the content between the first and second instances of PATTERN if pattern[1] - pattern[0] > int(d[1]): # too many lines, cut it pattern[1] = pattern[0] + int(d[1]) return (c[0], '\n'.join(lines[pattern[0]+1:pattern[1]])) return c return None %}!}
Then the following code will include the content between the first two headings from file myfile.bsmdoc. And the maximum number of lines is limited to 16.
#include ^(\\s)*=+|16|./myfile.bsmdoc
12 HTML Template
By default, bsmdoc uses the following template to generate the html file, which defines 4 sections (i.e., html, header, body and footer)
[html] begin = <!DOCTYPE html> <html> end= </html> [header] begin = <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> end = </head> content = bsmdoc_css = ['css/bsmdoc.css'] bsmdoc_js = ['js/bsmdoc.js'] menu_css = ['css/menu.css'] menu_js = ['js/menu.js'] mathjax = <script> MathJax = { loader: { load: ['[tex]/tagformat'] }, tex: { packages: {'[+]': ['tagformat']}, inlineMath: [['\\(', '\\)']], tags: "all", tagformat: { id: (id) => 'mjx-eqn-' + id.replace(/\s/g, '_'), } } }; </script> <script src=" https://cdn.jsdelivr.net/npm/mathjax@3.2.2/es5/tex-mml-chtml.min.js "></script> jquery = <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> [body] begin = <body class="nomathjax"> <div class="layout"> end = </div> </body> # default content is # %(article_menu)s # <div class="main"> # %(article_title)s # %(article_content)s # </div> content = [footer] begin = <div class="footer"> end = </div> content = <div class="footer-text"> Last updated %(UPDATED)s by <a href="http://bsmdoc.feiyilin.com/">bsmdoc</a>%(SOURCE)s.</div>
Using a separate configuration file seems not to be compatible with our goal to include everything in a single file, although sometimes it may be convenient (for example, to use the same configuration file for multiple docs). bsmdoc also provides a way to include the customized template in your doc, for example
{!config||{% [html] begin = <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> end= </html> ... %}!}
In this case, "config" function block is called without any argument. And its content should have a INI file structure.
bsmdoc uses ConfigParser to parse the configurations. Thus, %()s will be resolved to the corresponding configuration values. The configuration name is case insensitive. For example,
%(TITLE)s will be replaced with "my html title", if you config the title with
\config{title|my html title}
- %(UPDATED)s will be replaced with the time when the html file is generated.
- %(SOURCE)s will be replaced with the bsmdoc filename.
It is easy to create a configuration by
define the macro content, e.g.,
\config{MYMACRO|...}
use the configuration in the template
...%(MYMACRO}s...
bsmdoc defines "css
" configuration to insert additional css
in the html file without change the html template
\config{css|my.css}
To insert multiple css files, separate them with white-space
\config{css|my.css my2.css}
or
\config{css|my.css} \config{css|add|my2.css}
where add
option tells bsmdoc to append the configuration to the current one.
Similarly, bsmdoc also defines the "js
" configuration to include javascripts in the html file
\config{js|myjs.js myjs2.js}
There are two ways to define doc title. The first one is to use the toptitle
configuration. For example, the following line will define the top title
<div class="toptitle"> bsmdoc -- another technical html doc generator <div class="subtitle"> <a href='mailto:tq@feiyilin.com'>tq@feiyilin.com</a> </div> </div>
\config{doctitle|bsmdoc -- another technical html doc generator} \config{subtitle|[mailto:tq@feiyilin.com|tq@feiyilin.com]}
It is easy to see that it is equivalent to the following code
{!div|toptitle|| bsmdoc -- another technical html doc generator {!div|subtitle|| [mailto:tq@feiyilin.com|tq@feiyilin.com] !} !}